Function.RegisterNamespace("Test.System.Script.Attributes");

[Fixture]
Test.System.Script.Attributes.DecoratedFunction=function(){
    [Fixture]
    function DecoratedFunction(){
        [Fact]
        function ThrowsIfMethodIsOmitted(){
            var expected="System.Script.Attributes.DecoratedFunction.ctor: 'method' must be a valid Function pointer.";
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction();
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfMethodIsUndefined(){
            var expected="System.Script.Attributes.DecoratedFunction.ctor: 'method' must be a valid Function pointer.";
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(undefined);
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfMethodIsNull(){
            var expected="System.Script.Attributes.DecoratedFunction.ctor: 'method' must be a valid Function pointer.";
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(null);
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfMethodIsNotAValidFunction(){
            var expected="System.Script.Attributes.DecoratedFunction.ctor: 'method' must be a valid Function pointer.";
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsDecoratedMethod(){            
            var actual=new System.Script.Attributes.DecoratedFunction(function(){});
            
            Assert.True(Object.IsType(Function,actual));
        }
        
        [Fact]
        function AssignsGetDecorationMethod(){
            var actual=new System.Script.Attributes.DecoratedFunction(function(){}).GetDecoration;
            
            Assert.True(Object.IsType(Function,actual));
        }

        [Fact]
        function SetsNameOnDecoration(){
            var targetMethod=function(){};
            var expected="expected";
            
            var target=new System.Script.Attributes.DecoratedFunction(targetMethod,expected);
            var actual=target.GetDecoration().Name;
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function AddsSingleAttributeParameter(){
            var expected="expected";
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected);
            var actual=target.GetDecoration().GetAttributes()[0];
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function AddsMultipleSingleAttributeParameters(){
            var expected="expected";
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected,expected);
            var actual=target.GetDecoration().GetAttributes();
            
            Assert.Equal(2,actual.length);
        }

        [Fact]
        function AddsSingleAttributeInSingleAttributeCluster(){
            var expected=["expected"];
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected);
            var actual=target.GetDecoration().GetAttributes();
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function AddsMultipleAttributesInSingleAttributeCluster(){
            var expected=["expected1","expected2"];
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected);
            var actual=target.GetDecoration().GetAttributes();
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function AddsSingleAttributeInMultipleAttributeClusters(){
            var expected=["expected"];
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected,expected);
            var actual=target.GetDecoration().GetAttributes();
            
            Assert.Equal(2,actual.length);
        }

        [Fact]
        function AddsMultipleAttributesInMultipleAttributeClusters(){
            var expected=["expected1","expected2"];
            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected,expected);
            var actual=target.GetDecoration().GetAttributes();
            
            Assert.Equal(4,actual.length);
        }
        
        [Fact]
        function ProcessesAttributesOfTypeFunction(){
            var target=function(){
                actual=true;
            }
            var actual=false;
            new System.Script.Attributes.DecoratedFunction(function(){},null,target);            
            
            Assert.True(actual);
        }

        [Fact]
        function ThrowsIfFunctionAttributeThrows(){
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(function(){},null,function(){
                    throw new Error();
                });
            });
            
            Assert.NotNull(actual);
        }        
        
        [Fact]
        function ThrowsWhatFunctionAttributeThrows(){
            var targetExpected="target";
            var target=function(){
                throw targetExpected;
            }
            var expected=String.Format("System.Script.Attributes.DecoratedFunction.processAttributes: unable to instantiate attribute '{0}' at position [0].\nError: {1}",Function.GetName(target),targetExpected);

            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(function(){},null,target);
            });
            
            Assert.Equal(expected,actual);
        }
    }
    
    [Fixture]
    function DecorationWrapper(){
        [Fact]
        function IgnoresAttributesThatDontImplementIAttribute(){
            var target=function(){
                actual=true;
            };
            var actual=false;
            var targetAttribute=new Object();

            new System.Script.Attributes.DecoratedFunction(target,null,[targetAttribute])();

            Assert.True(actual);
        }

        [Fact]
        function CallsBeforeInvokeOnAttribute(){
            var target={
                BeforeInvoke:function(context){
                    actual=true;
                }
            };
            var actual=false;
            
            new System.Script.Attributes.DecoratedFunction(function(){},null,[target])();

            Assert.True(actual);
        }

        [Fact]
        function PassesContextToBeforeInvoke(){
            var target={
                BeforeInvoke:function(context){
                    actual=context;
                }
            };
            var actual=null;
            
            new System.Script.Attributes.DecoratedFunction(function(){},null,[target])();

            Assert.NotNull(actual);
        }
        
        [Fact]
        function DoesNotCallWrappedMethodIfAttributeSetsCancelOnContext(){
            var targetAttribute={
                BeforeInvoke:function(context){
                    context.Cancel=true;
                }
            };
            var target=function(){
                throw new Error("Test.System.Script.Attributes.DecoratedFunction.DecorationWrapper.DoesNotCallWrappedMethodIfAttributeSetsCancelOnContext: Called wrapped method.");
            };
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(target,null,[targetAttribute])();
            });
            
            Assert.Null(actual);
        }
        
        [Fact]
        function CallsWrappedMethod(){
            var target=function(){
                actual=true;
            };
            var actual=false;
            
            new System.Script.Attributes.DecoratedFunction(target)();
            
            Assert.True(actual);
        }
        
        [Fact]
        function SetsScopeOfWrappedMethodToDecoration(){
            var targetMethod=function(){
                actual=this;
            };
            var actual=null;
            
            var target=new System.Script.Attributes.DecoratedFunction(targetMethod);
            var expected=target.GetDecoration();
            target();
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function PassesArgumentToWrappedMethod(){
            var targetMethod=function(expectedArgument){
                actual=expectedArgument;
            };
            var expected="expected";
            var actual=null;
            
            var target=new System.Script.Attributes.DecoratedFunction(targetMethod);
            target(expected);
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesAllArgumentsToWrappedMethod(){
            var targetMethod=function(){
                actual=Array.prototype.slice.call(arguments,0);
            };
            var expected=["expected1","expected2","expected3"];
            var actual=null;
            
            var target=new System.Script.Attributes.DecoratedFunction(targetMethod);
            target(expected[0],expected[1],expected[2]);
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function WrappedMethodCanThrowError(){
            var expected=new Error("expected");
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(function(){
                    throw expected;
                })();
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function CallsAfterInvokeOnAttribute(){
            var target={
                AfterInvoke:function(context){
                    actual=true;
                }
            };
            var actual=false;
            
            new System.Script.Attributes.DecoratedFunction(function(){},null,[target])();

            Assert.True(actual);
        }

        [Fact]
        function PassesContextToAfterInvoke(){
            var target={
                AfterInvoke:function(context){
                    actual=context;
                }
            };
            var actual=null;
            
            new System.Script.Attributes.DecoratedFunction(function(){},null,[target])();

            Assert.NotNull(actual);
        }

        [Fact]
        function SetsMethodErrorOnContextForAfterInvoke(){
            var expected=new Error("expected");
            var target={
                AfterInvoke:function(context){
                    actual=context.Error;
                }
            };
            var actual=null;
            
            Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(function(){
                    throw expected;
                },null,[target])();
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function CanSuppressMethodErrorFromAfterInvoke(){
            var target={
                AfterInvoke:function(context){
                    context.Error=null;
                }
            };
            var actual=null;
            
            var actual=Record.Exception(function(){
                new System.Script.Attributes.DecoratedFunction(function(){
                    throw new Error();
                },null,[target])();
            });

            Assert.Null(actual);
        }

    }
    
    [Fixture]
    function GetDecoration(){
        [Fact]
        function GetDecorationMethodReturnsDecoration(){
            var actual=new System.Script.Attributes.DecoratedFunction(function(){}).GetDecoration();
            
            Assert.True(Object.IsType(System.Script.Attributes.DecoratedFunction,actual));
        }
    }
    
    [Fixture]
    function GetAttributes(){
        [Fact]
        function GetAttributesReturnsArray(){
            var target=new System.Script.Attributes.DecoratedFunction(function(){}).GetDecoration();
            
            var actual=target.GetAttributes();
            
            Assert.True(Object.IsType(Array,actual));
        }

        [Fact]
        function GetAttributesReturnsArray(){
            var target=new System.Script.Attributes.DecoratedFunction(function(){}).GetDecoration()
            
            var actual=target.GetAttributes();

            Assert.True(Object.IsType(Array,actual));
        }

        [Fact]
        function GetAttributesReturnsAllAttributes(){
            var expected=["expected1","expected2","expected3"];
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,expected).GetDecoration();
            
            var actual=target.GetAttributes();

            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function GetAttributesReturnsOnlyAttributeMatchingPatternExactly(){
            var expected=new System.Script.Attributes.Attribute("expected");
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[expected,"target1","target2"]).GetDecoration();
            
            var actual=target.GetAttributes(expected);

            Assert.Equal(1,actual.length);
        }

        [Fact]
        function GetAttributesReturnsAttributeMatchingPatternExactly(){
            var expected=new System.Script.Attributes.Attribute("expected");
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[expected,"target1","target2"]).GetDecoration();
            
            var actual=target.GetAttributes(expected)[0];

            Assert.Equal(expected,actual);
        }

        [Fact]
        function GetAttributesReturnsOnlyAttributeWithNameMatchingPattern(){
            var expected=new System.Script.Attributes.Attribute("expected");
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[expected,"target1","target2"]).GetDecoration();
            
            var actual=target.GetAttributes(expected.Name);

            Assert.Equal(1,actual.length);
        }

        [Fact]
        function GetAttributesReturnsAttributeWithNameMatchingPattern(){
            var expected=new System.Script.Attributes.Attribute("expected");
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[expected,"target1","target2"]).GetDecoration();
            
            var actual=target.GetAttributes(expected.Name)[0]

            Assert.Equal(expected,actual);
        }

        [Fact]
        function GetAttributesReturnsOnlyAttributeWithFunctionNameMatchingPattern(){
            var targetAttribute=function target(){};
            var targetMatch=Function.GetName(targetAttribute);
            targetAttribute.Inherit(System.Script.Attributes.Attribute);            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[targetAttribute,"target1","target2"]).GetDecoration();

            var actual=target.GetAttributes(targetMatch);

            Assert.Equal(1,actual.length);
        }

        [Fact]
        function GetAttributesReturnsAttributeWithFunctionNameMatchingPattern(){
            var expected=function target(){};
            var targetMatch=Function.GetName(expected);
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[expected,"target1","target2"]).GetDecoration();
            
            var actual=target.GetAttributes(targetMatch)[0].constructor;

            Assert.Equal(expected,actual);
        }

        [Fact]
        function GetAttributesReturnsOnlyAttributeWithMatchingConstructor(){
            var targetAttribute=function(){};
            targetAttribute.Inherit(System.Script.Attributes.Attribute);            
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[targetAttribute,"target1","target2"]).GetDecoration();

            var actual=target.GetAttributes(targetAttribute);

            Assert.Equal(1,actual.length);
        }

        [Fact]
        function GetAttributesReturnsAttributeWithMatchingConstructor(){
            var targetAttribute=function(){};
            targetAttribute.Inherit(System.Script.Attributes.Attribute);
            var target=new System.Script.Attributes.DecoratedFunction(function(){},null,[targetAttribute]).GetDecoration();
            var expected=target.GetAttributes()[0];

            var actual=target.GetAttributes(targetAttribute)[0];

            Assert.Equal(expected,actual);
        }

    }
    
    [Fixture]
    function GetMethod(){
        [Fact]
        function ReturnsFunctionPassedAsMethodToConstructor(){
            var expected=function(){};
            
            var target=new System.Script.Attributes.DecoratedFunction(expected);
            var actual=target.GetDecoration().GetMethod();
            
            Assert.Equal(expected,actual);
        }
    }
}
